/* -------------------------------------------------------------------------
 *
 * fe-print.cpp
 *	  functions for pretty-printing query results
 *
 * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd.
 * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * These functions were formerly part of fe-exec.c, but they
 * didn't really belong there.
 *
 * IDENTIFICATION
 *	  src/common/interfaces/libpq/fe-print.cpp
 *
 * -------------------------------------------------------------------------
 */
#include "postgres_fe.h"

#include <signal.h>

#ifdef WIN32
#include "win32.h"
#else
#include <unistd.h>
#include <sys/ioctl.h>
#endif

#ifdef HAVE_TERMIOS_H
#include <termios.h>
#else
#ifndef WIN32
#include <sys/termios.h>
#endif
#endif

#include "libpq/libpq-fe.h"
#include "libpq/libpq-int.h"
#include "fe-auth.h"
#include "pqsignal.h"

static void do_field(const PQprintOpt* po, const PGresult* res, const int i, const int j, const int fs_len,
    char** fields, const int nFields, const char** fieldNames, unsigned char* fieldNotNum, int* fieldMax,
    const int fieldMaxLen, FILE* fout);
static char* do_header(FILE* fout, const PQprintOpt* po, const int nFields, int* fieldMax, const char** fieldNames,
    unsigned char* fieldNotNum, const int fs_len, const PGresult* res);
static void output_row(FILE* fout, const PQprintOpt* po, const int nFields, char** fields, unsigned char* fieldNotNum,
    const int* fieldMax, char* border, const int row_index);
static void fill(int length, int max, char filler, FILE* fp);

/*
 * PQprint()
 *
 * Format results of a query for printing.
 *
 * PQprintOpt is a typedef (structure) that contains
 * various flags and options. consult libpq-fe.h for
 * details
 *
 * This function should probably be removed sometime since psql
 * doesn't use it anymore. It is unclear to what extent this is used
 * by external clients, however.
 */
void PQprint(FILE* fout, const PGresult* res, const PQprintOpt* po)
{
    int nFields;

    nFields = PQnfields(res);

    if (nFields > 0) { /* only print rows with at least 1 field.  */
        int i, j;
        int nTups;
        int* fieldMax = NULL; /* in case we don't use them */
        unsigned char* fieldNotNum = NULL;
        char* border = NULL;
        char** fields = NULL;
        const char** fieldNames = NULL;
        int fieldMaxLen = 0;
        int numFieldName;
        int fs_len = strlen(po->fieldSep);
        int total_line_length = 0;
        int usePipe = 0;
        char* pagerenv = NULL;

#if defined(ENABLE_THREAD_SAFETY) && !defined(WIN32)
        sigset_t osigset;
        bool sigpipe_masked = false;
        bool sigpipe_pending = false;
#endif
#if !defined(ENABLE_THREAD_SAFETY) && !defined(WIN32)
        pqsigfunc oldsigpipehandler = NULL;
#endif

#ifdef TIOCGWINSZ
        struct winsize screen_size;
#else
        struct winsize {
            int ws_row;
            int ws_col;
        } screen_size;
#endif

        nTups = PQntuples(res);
        if ((fieldNames = (const char**)calloc(nFields, sizeof(char*))) == NULL) {
            fprintf(stderr, libpq_gettext("out of memory\n"));
            abort();
        }
        if ((fieldNotNum = (unsigned char*)calloc(nFields, 1)) == NULL) {
            fprintf(stderr, libpq_gettext("out of memory\n"));
            abort();
        }
        if ((fieldMax = (int*)calloc(nFields, sizeof(int))) == NULL) {
            fprintf(stderr, libpq_gettext("out of memory\n"));
            abort();
        }
        for (numFieldName = 0; (po->fieldName != NULL) && (po->fieldName[numFieldName] != NULL); numFieldName++)
            ;
        for (j = 0; j < nFields; j++) {
            int len;
            const char* s = (j < numFieldName && po->fieldName[j][0]) ? po->fieldName[j] : PQfname(res, j);

            fieldNames[j] = s;
            len = s != NULL ? strlen(s) : 0;
            fieldMax[j] = len;
            len += fs_len;
            if (len > fieldMaxLen)
                fieldMaxLen = len;
            total_line_length += len;
        }

        total_line_length += nFields * strlen(po->fieldSep) + 1;

        if (fout == NULL)
            fout = stdout;
        if (po->pager && fout == stdout && isatty(fileno(stdin)) && isatty(fileno(stdout))) {
            /*
             * If we think there'll be more than one screen of output, try to
             * pipe to the pager program.
             */
#ifdef TIOCGWINSZ
            if (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) == -1 || screen_size.ws_col == 0 ||
                screen_size.ws_row == 0) {
                screen_size.ws_row = 24;
                screen_size.ws_col = 80;
            }
#else
            screen_size.ws_row = 24;
            screen_size.ws_col = 80;
#endif
            char* tmp = gs_getenv_r("PAGER");
            if (check_client_env(tmp) == NULL) {
                pagerenv = NULL;
            } else {
                pagerenv = strdup(tmp);
            }

            if (pagerenv != NULL && pagerenv[0] != '\0' && !po->html3 &&
                ((po->expanded && nTups * (nFields + 1) >= screen_size.ws_row) ||
                    (!po->expanded &&
                        nTups * (total_line_length / screen_size.ws_col + 1) * (1 + (int)(po->standard != 0)) >=
                            screen_size.ws_row - (int)(po->header != 0) * (total_line_length / screen_size.ws_col + 1)
                            * 2 - (int)(po->header != 0) * 2 /* row count and newline */
                        ))) {
                if (check_client_env(pagerenv) == NULL) {
                    fprintf(stderr, libpq_gettext("check parameter failed.\n"));
                    abort();
                }

                fout = popen(pagerenv, "w");
                if (fout != NULL) {
                    usePipe = 1;
#ifndef WIN32
#ifdef ENABLE_THREAD_SAFETY
                    if (pq_block_sigpipe(&osigset, &sigpipe_pending) == 0)
                        sigpipe_masked = true;
#else
                    oldsigpipehandler = pqsignal(SIGPIPE, SIG_IGN);
#endif /* ENABLE_THREAD_SAFETY */
#endif /* WIN32 */
                } else
                    fout = stdout;
            }
        }

        if (!po->expanded && (po->align || po->html3)) {
            if ((fields = (char**)calloc(nFields * (nTups + 1), sizeof(char*))) == NULL) {
                fprintf(stderr, libpq_gettext("out of memory\n"));
                abort();
            }
        } else if (po->header && !po->html3) {
            if (po->expanded) {
                if (po->align)
                    fprintf(fout,
                        libpq_gettext("%-*s%s Value\n"),
                        fieldMaxLen - fs_len,
                        libpq_gettext("Field"),
                        po->fieldSep);
                else
                    fprintf(fout, libpq_gettext("%s%sValue\n"), libpq_gettext("Field"), po->fieldSep);
            } else {
                int len = 0;

                for (j = 0; j < nFields; j++) {
                    const char* s = fieldNames[j];

                    fputs(s, fout);
                    len += strlen(s) + fs_len;
                    if ((j + 1) < nFields)
                        fputs(po->fieldSep, fout);
                }
                fputc('\n', fout);
                for (len -= fs_len; len--; fputc('-', fout))
                    ;
                fputc('\n', fout);
            }
        }
        if (po->expanded && po->html3) {
            if (po->caption != NULL)
                fprintf(fout, "<center><h2>%s</h2></center>\n", po->caption);
            else
                fprintf(fout,
                    "<center><h2>"
                    "Query retrieved %d rows * %d fields"
                    "</h2></center>\n",
                    nTups,
                    nFields);
        }
        for (i = 0; i < nTups; i++) {
            if (po->expanded) {
                if (po->html3)
                    fprintf(fout,
                        "<table %s><caption align=\"top\">%d</caption>\n",
                        po->tableOpt != NULL ? po->tableOpt : "",
                        i);
                else
                    fprintf(fout, libpq_gettext("-- RECORD %d --\n"), i);
            }
            for (j = 0; j < nFields; j++)
                do_field(po, res, i, j, fs_len, fields, nFields, fieldNames, fieldNotNum, fieldMax, fieldMaxLen, fout);
            if (po->html3 && po->expanded)
                fputs("</table>\n", fout);
        }
        if (!po->expanded && (po->align || po->html3)) {
            if (po->html3) {
                if (po->header) {
                    if (po->caption != NULL)
                        fprintf(fout,
                            "<table %s><caption align=\"top\">%s</caption>\n",
                            po->tableOpt != NULL ? po->tableOpt : "",
                            po->caption);
                    else
                        fprintf(fout,
                            "<table %s><caption align=\"top\">"
                            "Retrieved %d rows * %d fields"
                            "</caption>\n",
                            po->tableOpt != NULL ? po->tableOpt : "",
                            nTups,
                            nFields);
                } else
                    fprintf(fout, "<table %s>", po->tableOpt != NULL ? po->tableOpt : "");
            }
            if (po->header)
                border = do_header(fout, po, nFields, fieldMax, fieldNames, fieldNotNum, fs_len, res);
            for (i = 0; i < nTups; i++)
                output_row(fout, po, nFields, fields, fieldNotNum, fieldMax, border, i);
            libpq_free(fields);
            libpq_free(border);
        }
        if (po->header && !po->html3)
            fprintf(fout, "(%d row%s)\n\n", PQntuples(res), (PQntuples(res) == 1) ? "" : "s");
        libpq_free(fieldMax);
        libpq_free(fieldNotNum);
        free((void*)fieldNames);
        fieldNames = NULL;
        if (usePipe) {
#ifdef WIN32
            _pclose(fout);
#else
            pclose(fout);

#ifdef ENABLE_THREAD_SAFETY
            /* we can't easily verify if EPIPE occurred, so say it did */
            if (sigpipe_masked)
                pq_reset_sigpipe(&osigset, sigpipe_pending, true);
#else
            pqsignal(SIGPIPE, oldsigpipehandler);
#endif /* ENABLE_THREAD_SAFETY */
#endif /* WIN32 */
        }
        if (po->html3 && !po->expanded)
            fputs("</table>\n", fout);
        libpq_free(pagerenv);
    }
}

static void do_field(const PQprintOpt* po, const PGresult* res, const int i, const int j, const int fs_len,
    char** fields, const int nFields, char const** fieldNames, unsigned char* fieldNotNum, int* fieldMax,
    const int fieldMaxLen, FILE* fout)
{
    const char* pval = NULL;
    const char* p = NULL;

    int plen;
    bool skipit = false;

    plen = PQgetlength(res, i, j);
    pval = PQgetvalue(res, i, j);

    if (plen < 1 || (pval == NULL) || !*pval) {
        if (po->align || po->expanded) {
            skipit = true;
        } else {
            goto efield;
        }
    } else {
        skipit = false;
    }

    if (!skipit) {
        if (po->align && !fieldNotNum[j]) {
            /* Detect whether field contains non-numeric data */
            char ch = '0';

            for (p = pval; *p; p += PQmblen(p, res->client_encoding)) {
                ch = *p;
                if (!((ch >= '0' && ch <= '9') || ch == '.' || ch == 'E' || ch == 'e' || ch == ' ' || ch == '-')) {
                    fieldNotNum[j] = 1;
                    break;
                }
            }

            /*
             * Above loop will believe E in first column is numeric; also, we
             * insist on a digit in the last column for a numeric. This test
             * is still not bulletproof but it handles most cases.
             */
            if (*pval == 'E' || *pval == 'e' || !(ch >= '0' && ch <= '9')) {
                fieldNotNum[j] = 1; 
            }
        }

        if (!po->expanded && (po->align || po->html3)) {
            if (plen > fieldMax[j]) {
                fieldMax[j] = plen;
            }
            if ((fields[i * nFields + j] = (char*)malloc(plen + 1)) == NULL) {
                fprintf(stderr, libpq_gettext("out of memory\n"));
                abort();
            }
            check_strcpy_s(strcpy_s(fields[i * nFields + j], plen + 1, pval));
        } else {
            if (po->expanded) {
                if (po->html3)
                    fprintf(fout,
                        "<tr><td align=\"left\"><b>%s</b></td>"
                        "<td align=\"%s\">%s</td></tr>\n",
                        fieldNames[j],
                        fieldNotNum[j] ? "left" : "right",
                        pval);
                else {
                    if (po->align)
                        fprintf(fout, "%-*s%s %s\n", fieldMaxLen - fs_len, fieldNames[j], po->fieldSep, pval);
                    else
                        fprintf(fout, "%s%s%s\n", fieldNames[j], po->fieldSep, pval);
                }
            } else {
                if (!po->html3) {
                    fputs(pval, fout);
                efield:
                    if ((j + 1) < nFields)
                        fputs(po->fieldSep, fout);
                    else
                        fputc('\n', fout);
                }
            }
        }
    }
}

static char* do_header(FILE* fout, const PQprintOpt* po, const int nFields, int* fieldMax, const char** fieldNames,
    unsigned char* fieldNotNum, const int fs_len, const PGresult* res)
{

    int j; /* for loop index */
    char* border = NULL;

    if (po->html3)
        fputs("<tr>", fout);
    else {
        int tot = 0;
        int n = 0;
        char* p = NULL;

        for (; n < nFields; n++)
            tot += fieldMax[n] + fs_len + (po->standard ? 2 : 0);
        if (po->standard)
            tot += fs_len * 2 + 2;
        border = (char*)malloc(tot + 1);
        if (border == NULL) {
            fprintf(stderr, libpq_gettext("out of memory\n"));
            abort();
        }
        p = border;
        if (po->standard) {
            char* fs = po->fieldSep;

            while (*fs++)
                *p++ = '+';
        }
        for (j = 0; j < nFields; j++) {
            int len;

            for (len = fieldMax[j] + (po->standard ? 2 : 0); len--; *p++ = '-')
                ;
            if (po->standard || (j + 1) < nFields) {
                char* fs = po->fieldSep;

                while (*fs++)
                    *p++ = '+';
            }
        }
        *p = '\0';
        if (po->standard)
            fprintf(fout, "%s\n", border);
    }
    if (po->standard)
        fputs(po->fieldSep, fout);
    for (j = 0; j < nFields; j++) {
        const char* s = PQfname(res, j);

        if (po->html3) {
            fprintf(fout, "<th align=\"%s\">%s</th>", fieldNotNum[j] ? "left" : "right", fieldNames[j]);
        } else {
            int n = strlen(s);

            if (n > fieldMax[j])
                fieldMax[j] = n;
            if (po->standard)
                fprintf(fout, fieldNotNum[j] ? " %-*s " : " %*s ", fieldMax[j], s);
            else
                fprintf(fout, fieldNotNum[j] ? "%-*s" : "%*s", fieldMax[j], s);
            if (po->standard || (j + 1) < nFields)
                fputs(po->fieldSep, fout);
        }
    }
    if (po->html3)
        fputs("</tr>\n", fout);
    else
        fprintf(fout, "\n%s\n", border);
    return border;
}

static void output_row(FILE* fout, const PQprintOpt* po, const int nFields, char** fields, unsigned char* fieldNotNum,
    const int* fieldMax, char* border, const int row_index)
{

    int field_index; /* for loop index */

    if (po->html3)
        fputs("<tr>", fout);
    else if (po->standard)
        fputs(po->fieldSep, fout);
    for (field_index = 0; field_index < nFields; field_index++) {
        char* p = fields[row_index * nFields + field_index];

        if (po->html3)
            fprintf(fout, "<td align=\"%s\">%s</td>", fieldNotNum[field_index] ? "left" : "right", p != NULL ? p : "");
        else {
            fprintf(fout,
                fieldNotNum[field_index] ? (po->standard ? " %-*s " : "%-*s") : (po->standard ? " %*s " : "%*s"),
                fieldMax[field_index],
                p != NULL ? p : "");
            if (po->standard || field_index + 1 < nFields)
                fputs(po->fieldSep, fout);
        }
        libpq_free(p);
    }
    if (po->html3)
        fputs("</tr>", fout);
    else if (po->standard)
        fprintf(fout, "\n%s", border);
    fputc('\n', fout);
}

/*
 * really old printing routines
 */

void PQdisplayTuples(const PGresult* res, FILE* fp, /* where to send the output */
    int fillAlign,                                  /* pad the fields with spaces */
    const char* fieldSep,                           /* field separator */
    int printHeader,                                /* display headers? */
    int quiet)
{
#define DEFAULT_FIELD_SEP " "

    int i, j;
    int nFields;
    int nTuples;
    int* fLength = NULL;

    if (fieldSep == NULL) {
        fieldSep = DEFAULT_FIELD_SEP;
    }

    /* Get some useful info about the results */
    nFields = PQnfields(res);
    nTuples = PQntuples(res);

    if (fp == NULL)
        fp = stdout;

    /* Figure the field lengths to align to */
    /* will be somewhat time consuming for very large results */
    if (fillAlign) {
        fLength = (int*)malloc(nFields * sizeof(int));
        if (fLength == NULL) {
            fprintf(stderr, libpq_gettext("out of memory\n"));
            abort();
        }

        for (j = 0; j < nFields; j++) {
            fLength[j] = strlen(PQfname(res, j));
            for (i = 0; i < nTuples; i++) {
                int flen = PQgetlength(res, i, j);

                if (flen > fLength[j]) {
                    fLength[j] = flen;
                }
            }
        }
    }

    if (printHeader) {
        /* first, print out the attribute names */
        for (i = 0; i < nFields; i++) {
            fputs(PQfname(res, i), fp);
            if (fillAlign)
                fill(strlen(PQfname(res, i)), fLength[i], ' ', fp);
            fputs(fieldSep, fp);
        }
        fprintf(fp, "\n");

        /* Underline the attribute names */
        for (i = 0; i < nFields; i++) {
            if (fillAlign)
                fill(0, fLength[i], '-', fp);
            fputs(fieldSep, fp);
        }
        fprintf(fp, "\n");
    }

    /* next, print out the instances */
    for (i = 0; i < nTuples; i++) {
        for (j = 0; j < nFields; j++) {
            fprintf(fp, "%s", PQgetvalue(res, i, j));
            if (fillAlign)
                fill(strlen(PQgetvalue(res, i, j)), fLength[j], ' ', fp);
            fputs(fieldSep, fp);
        }
        fprintf(fp, "\n");
    }

    if (!quiet)
        fprintf(fp, "\nQuery returned %d row%s.\n", PQntuples(res), (PQntuples(res) == 1) ? "" : "s");

    fflush(fp);

    libpq_free(fLength);
}

void PQprintTuples(const PGresult* res, FILE* fout, /* output stream */
    int PrintAttNames,                              /* print attribute names or not */
    int TerseOutput,                                /* delimiter bars or not? */
    int colWidth                                    /* width of column, if 0, use variable width */
)
{
    int nFields;
    int nTups;
    int i, j;
    char formatString[80];
    char* tborder = NULL;
    int rcs = 0;

    nFields = PQnfields(res);
    nTups = PQntuples(res);

    if (colWidth > 0)
        rcs = sprintf_s(formatString, sizeof(formatString), "%%s %%-%ds", colWidth);
    else
        rcs = sprintf_s(formatString, sizeof(formatString), "%%s %%s");

    securec_check_ss_c(rcs, "\0", "\0");
    if (nFields > 0) { /* only print rows with at least 1 field.  */

        if (!TerseOutput) {
            int width;

            width = nFields * 14;
            tborder = (char*)malloc(width + 1);
            if (tborder == NULL) {
                fprintf(stderr, libpq_gettext("out of memory\n"));
                abort();
            }
            for (i = 0; i < width; i++)
                tborder[i] = '-';
            tborder[width] = '\0';
            fprintf(fout, "%s\n", tborder);
        }

        for (i = 0; i < nFields; i++) {
            if (PrintAttNames) {
                fprintf(fout, formatString, TerseOutput ? "" : "|", PQfname(res, i));
            }
        }

        if (PrintAttNames) {
            if (TerseOutput)
                fprintf(fout, "\n");
            else
                fprintf(fout, "|\n%s\n", tborder);
        }

        for (i = 0; i < nTups; i++) {
            for (j = 0; j < nFields; j++) {
                const char* pval = PQgetvalue(res, i, j);

                fprintf(fout, formatString, TerseOutput ? "" : "|", pval != NULL ? pval : "");
            }
            if (TerseOutput)
                fprintf(fout, "\n");
            else
                fprintf(fout, "|\n%s\n", tborder);
        }
    }

    libpq_free(tborder);
}

/* simply send out max-length number of filler characters to fp */

static void fill(int length, int max, char filler, FILE* fp)
{
    int count;

    count = max - length;
    while (count-- >= 0)
        putc(filler, fp);
}
